package com.github.ecbp.user.service.service.impl;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.ecbp.common.constant.CacheKeyConstant;
import com.github.ecbp.common.constant.ConstantCode;
import com.github.ecbp.common.data.entity.BaseEntity;
import com.github.ecbp.common.data.service.impl.BaseServiceImpl;
import com.github.ecbp.common.data.utils.PagingUtil;
import com.github.ecbp.common.data.utils.TransformUtils;
import com.github.ecbp.common.exception.user.NotLoginException;
import com.github.ecbp.common.redis.RedisUtil;
import com.github.ecbp.common.req.BaseIdListDTO;
import com.github.ecbp.common.req.BasePageDTO;
import com.github.ecbp.common.resp.BaseVO;
import com.github.ecbp.common.resp.CaptchaVO;
import com.github.ecbp.common.security.utils.CommonAdminUtils;
import com.github.ecbp.common.security.utils.JwtTokenUtil;
import com.github.ecbp.common.security.vo.DefaultUserPermission;
import com.github.ecbp.common.utils.*;
import com.github.ecbp.user.service.common.UserCodeAndMessageEnum;
import com.github.ecbp.user.service.config.PropertiesConfig;
import com.github.ecbp.user.service.dto.AdminLoginDto;
import com.github.ecbp.user.service.dto.AdminQueryDto;
import com.github.ecbp.user.service.dto.PhoneDto;
import com.github.ecbp.user.service.entity.TAdmin;
import com.github.ecbp.user.service.entity.TPermission;
import com.github.ecbp.user.service.mapper.AdminMapper;
import com.github.ecbp.user.service.response.AdminRes;
import com.github.ecbp.user.service.service.AdminRoleRelationService;
import com.github.ecbp.user.service.service.AdminService;
import com.github.ecbp.user.service.service.PermissionService;
import com.github.ecbp.user.service.vo.AdminPageVo;
import com.github.ecbp.user.service.vo.AdminRoleIdVo;
import com.github.ecbp.user.service.vo.AdminUserDetails;
import com.github.ecbp.user.service.vo.AdminVo;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 后台用户表 服务实现类
 * </p>
 *
 * @author zj2626
 * @since 2020-03-18
 */
@Service
public class AdminServiceImpl extends BaseServiceImpl<AdminMapper, TAdmin> implements AdminService {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PropertiesConfig propertiesConfig;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AdminRoleRelationService adminRoleRelationService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;

    @Override
    public IPage<AdminPageVo> page(AdminQueryDto param) {
        TAdmin admin = TransformUtils.copy(param, TAdmin.class);
        QueryWrapper<TAdmin> wrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(param.getRoleId())) {
            wrapper.eq("arr.role_id", param.getRoleId());
        }
        assemble(admin, wrapper, param);
        IPage<AdminPageVo> iPage = new Page<>(PagingUtil.getPage(), PagingUtil.getLimit());
        iPage.setRecords(baseMapper.selectPageWithRole(iPage, wrapper));
        if (!CollectionUtils.isEmpty(iPage.getRecords())) {
            final List<AdminRoleIdVo> roleNameByAdminId =
                    adminRoleRelationService.getRoleIdByAdminId(iPage.getRecords().stream().map(BaseVO::getId).collect(Collectors.toSet()));
            final Map<Long, List<String>> stringEnumResMap =
                    roleNameByAdminId.stream().collect(Collectors.groupingBy(AdminRoleIdVo::getAdminId,
                            Collectors.mapping(AdminRoleIdVo::getRoleName, Collectors.toList())));
            iPage.getRecords().forEach(record -> {
                record.setRoleNames(stringEnumResMap.get(record.getId()));
            });
        }
        return iPage;
    }

    private TAdmin getAdminByloginPhone(String loginPhone) {
        LambdaQueryWrapper<TAdmin> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(TAdmin::getLoginPhone, loginPhone);
        wrapper.eq(TAdmin::getStatus, ConstantCode.TRUE);
        wrapper.last("limit 1");
        List<TAdmin> adminList = baseMapper.selectList(wrapper);
        return CollectionUtils.isEmpty(adminList) ? null : adminList.get(0);
    }

    @Override
    public void saveImageCode() {
        try {
            response.setContentType("image/gif");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);

            // 生成验证码图片
            CaptchaVO iCaptcha = CaptchaUtils.getImageNew(response.getOutputStream());
            String code = iCaptcha.getCode();
            if (logger.isDebugEnabled()) {
                logger.debug("生成验证码为: {}", code);
            }
            // 保存验证码到redis
            ImmutableMap<String, String> immutableMap =
                    ImmutableMap.of(CacheKeyConstant.KEY, CacheKeyConstant.CODE_PREFIX,
                            CacheKeyConstant.IP, IpAddressUtils.getIpAddress(request),
                            CacheKeyConstant.OS, IpAddressUtils.getOsAndBrowserInfo(request));
            redisUtil.set(PlaceHolderUtils.resolverString(CacheKeyConstant.CODE_IN_REDIS, immutableMap), code,
                    propertiesConfig.getTimeout(), 0);
        } catch (Exception e) {
            logger.error("获取验证码", e);
        }
    }

    public void removeImageCode() {
        // 保存验证码到redis
        ImmutableMap<String, String> immutableMap =
                ImmutableMap.of(CacheKeyConstant.KEY, CacheKeyConstant.CODE_PREFIX,
                        CacheKeyConstant.IP, IpAddressUtils.getIpAddress(request),
                        CacheKeyConstant.OS, IpAddressUtils.getOsAndBrowserInfo(request));
        redisUtil.del(PlaceHolderUtils.resolverString(CacheKeyConstant.CODE_IN_REDIS, immutableMap));
    }

    public String getImageCode() {
        // 获取验证码到redis
        ImmutableMap<String, String> immutableMap =
                ImmutableMap.of(CacheKeyConstant.KEY, CacheKeyConstant.CODE_PREFIX,
                        CacheKeyConstant.IP, IpAddressUtils.getIpAddress(request),
                        CacheKeyConstant.OS, IpAddressUtils.getOsAndBrowserInfo(request));
        return redisUtil.get(PlaceHolderUtils.resolverString(CacheKeyConstant.CODE_IN_REDIS, immutableMap), 0);
    }

    @Override
    public String login(AdminLoginDto param) {
        String token = null;
        // 密码需要客户端加密后传递 验证码校验
        String codeInRedis = getImageCode();
        Asserts.checkNotNull(param.getCode(), UserCodeAndMessageEnum.USER_ICAPTCHA_CODE_ERROR);
        Asserts.isTrue(param.getCode().equalsIgnoreCase(codeInRedis), UserCodeAndMessageEnum.USER_ICAPTCHA_CODE_ERROR);
        removeImageCode();

        TAdmin userInfo = null;
        try {
            userInfo = getAdminByloginPhone(param.getLoginPhone());
            Asserts.checkNotNull(userInfo, UserCodeAndMessageEnum.USERNAME_PASSWORD_ERROR);
        } catch (Exception e) {
            LambdaQueryWrapper<TAdmin> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(TAdmin::getLoginPhone, param.getLoginPhone());
            wrapper.eq(TAdmin::getStatus, ConstantCode.FALSE);
            Asserts.isTrue(baseMapper.selectCount(wrapper) == 0, UserCodeAndMessageEnum.USERNAME_FORBIDDEN_ERROR);
            logger.error("登录异常", e);
            Asserts.checkHandle(0, UserCodeAndMessageEnum.USERNAME_PASSWORD_ERROR);

        }
        boolean passwordRight = passwordEncoder.matches(jwtTokenUtil.getSalt() + param.getPassword(), userInfo.getPassword());
        Asserts.isTrue(passwordRight, UserCodeAndMessageEnum.USERNAME_PASSWORD_ERROR);

        final AdminUserDetails userDetails = loadUserDetails(userInfo);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        token = jwtTokenUtil.generateToken(userDetails);
        updateLoginTimeById(userInfo.getId());

        // 顶掉前面登录的token(不同的系统不会顶掉)
        jwtTokenUtil.removeTokenFromRedis(param.getLoginPhone());
        // 设置用户token
        jwtTokenUtil.setTokenToRedis(param.getLoginPhone(), token);
        // 设置用户信息
        jwtTokenUtil.setUserInfo(param.getLoginPhone(), JSON.toJSONString(userDetails));

        logger.info("登录成功 [{}] [{}]", param.getLoginPhone(), token);
        return token;
    }

    @Override
    public void logout() {
        String loginPhone = CommonAdminUtils.getLoginPhoneFromToken();
        if (StringUtils.isEmpty(loginPhone) || CacheKeyConstant.UNKNOWN.equals(loginPhone)) {
            throw new NotLoginException();
        }
        jwtTokenUtil.removeTokenFromRedis(loginPhone);
    }

    @Override
    public void logout(BaseIdListDTO param) {
        List<TAdmin> admins = this.listByIds(param.getIds());
        Asserts.checkListNotNull(admins, UserCodeAndMessageEnum.USER_NOT_EXISTS_ERROR);

        for (TAdmin tAdmin : admins) {
            jwtTokenUtil.removeTokenFromRedis(tAdmin.getLoginPhone());
        }
        logger.info("被注销登录的用户:{}", admins);
        for (TAdmin admin : admins) {
            // 发送短信
        }
    }

    /**
     * 根据用户名修改登录时间
     */
    private void updateLoginTimeById(Long id) {
        TAdmin record = new TAdmin();
        record.setLoginTime(new Date());
        LambdaUpdateWrapper<TAdmin> wrapper = new LambdaUpdateWrapper<>(getEntityClass());
        wrapper.eq(TAdmin::getId, id);
        baseMapper.update(record, wrapper);
    }

    /**
     * 发送验证码
     * <p>
     * 1.验证用户存在
     * 2.生成验证码,存储 默认90s
     * 3.发送短信
     *
     * @param param
     * @return
     */
    @Override
    public String sendVerification(PhoneDto param) {
        LambdaQueryWrapper<TAdmin> wrapper = new LambdaQueryWrapper<>(getEntityClass()).select(BaseEntity::getId);
        wrapper.eq(TAdmin::getLoginPhone, param.getLoginPhone());
        wrapper.last("limit 1");
        List<TAdmin> entitys = baseMapper.selectList(wrapper);
        Asserts.checkListNotNull(entitys, UserCodeAndMessageEnum.USER_NOT_EXISTS_ERROR);

        // 生成验证码
        String code = RandomStringUtils.getRandomNumberString(6);
        logger.info("用户[{}]生产的验证码[{}]", param.getLoginPhone(), code);

        /*
        发送短信:
        每天最多修改5次密码, 每天最多给同一注册手机号发送5条找回密码验证短信。
         */
        Map<String, Object> sendMap = new HashMap<>(1);
        sendMap.put("code", code);
        // sendMsgService.send(param.getLoginPhone(), sendMap);

        // 保存验证码到redis
        ImmutableMap<String, String> immutableMap = ImmutableMap.of(
                CacheKeyConstant.KEY, CacheKeyConstant.CODE_PREFIX,
                CacheKeyConstant.PHONE, param.getLoginPhone());
        redisUtil.set(PlaceHolderUtils.resolverString(CacheKeyConstant.VERIFICATION_IN_REDIS, immutableMap), code,
                propertiesConfig.getVerificationTimeout(), 0);

        return code;
    }

    @Override
    public AdminRes getAdminInfoById(Long id) {
        return TransformUtils.copy(baseMapper.selectById(id), AdminRes.class);
    }

    /**
     * 获得用户信息, 返回UserDetails类型
     *
     * @param loginPhone
     * @return
     */
    @Override
    public AdminUserDetails loadUserByLoginPhone(String loginPhone) {
        //获取用户信息
        TAdmin admin = getAdminByloginPhone(loginPhone);
        if (null == admin) {
            return null;
        }
        return loadUserDetails(admin);
    }

    private AdminUserDetails loadUserDetails(TAdmin admin) {
        DefaultUserPermission userPermission = new DefaultUserPermission();
        List<TPermission> permissionList = permissionService.getPermissionList(admin.getId(), userPermission);
        return new AdminUserDetails(admin, userPermission, permissionList);
    }

    @Override
    public void reloadUserPermission(Set<String> loginPhones) {
        for (String loginPhone : loginPhones) {
            Set<String> keys = jwtTokenUtil.hasUserInfo(loginPhone);
            if (!CollectionUtils.isEmpty(keys)) {
                // 设置用户信息
                UserDetails userDetails = loadUserByLoginPhone(loginPhone);
                if (null != userDetails) {
                    for (String key : keys) {
                        jwtTokenUtil.setUserInfoByKey(key, JSON.toJSONString(userDetails));
                    }
                }
            }
        }
    }

    @Override
    public AdminVo getById(Long id) {
        return TransformUtils.copy(baseMapper.selectById(id), AdminVo.class);
    }

    protected void assemble(TAdmin entity, QueryWrapper<TAdmin> wrapper, BasePageDTO pageDTO) {
        wrapper.select("distinct a.id", "a.login_phone", "a.username", "a.empno", "a.status", "a.customer_type",
                "a.update_time", "a.create_time");
        if (null != entity.getId()) {
            wrapper.eq("a.id", entity.getId());
        }
        if (!StringUtils.isEmpty(entity.getLoginPhone())) {
            wrapper.likeRight("a.login_phone", entity.getLoginPhone());
        }
        if (null != pageDTO) {
            if (null != pageDTO.getStartDate()) {
                wrapper.ge("a.update_time", pageDTO.getStartDate());
            }
            if (null != pageDTO.getEndDate()) {
                wrapper.le("a.update_time", pageDTO.getEndDate());
            }
        }
        if (null != entity.getStatus()) {
            wrapper.eq("a.status", entity.getStatus());
        }
        wrapper.eq("a.delete_status", ConstantCode.TRUE);
        wrapper.eq("arr.delete_status", ConstantCode.TRUE);
        if (!StringUtils.isEmpty(entity.getUsername())) {
            wrapper.likeRight("a.username", entity.getUsername());
        }

        wrapper.orderByDesc("a.create_time");
    }
}
